package com.amkai.aes.webservice.binary.objecthandler.casehistory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;

import com.amkai.aes.aescommon.casecosting.CdmLogBillingStatus;
import com.amkai.aes.aescommon.casehistory.AdjustmentDescriptorHelper;
import com.amkai.aes.aescommon.casehistory.ProcedureLogUtils;
import com.amkai.aes.aescommon.casehistory.ResourceLogUtils;
import com.amkai.aes.aescommon.casehistory.SupplyLogUtils;
import com.amkai.aes.aescommon.dictionary.ItemManager;
import com.amkai.aes.aescommon.exceptions.inventory.AdjustmentManagerException;
import com.amkai.aes.aescommon.ledger.managers.details.ProcedureUtils;
import com.amkai.aes.aescommon.ledger.utils.SourceOfRevenueUtils;
import com.amkai.aes.aescommon.organization.FacilityManager;
import com.amkai.aes.aescommon.schedule.AbstractAppointmentUtils;
import com.amkai.aes.aescommon.service.binary.job.casehistory.CaseHistoryJob;
import com.amkai.aes.aescommon.service.binary.job.casehistory.CaseHistoryJobResult;
import com.amkai.aes.aescommon.service.binary.job.inventory.InventoryJobResult;
import com.amkai.aes.aescommon.service.scheduling.AppointmentStatusChangeResult;
import com.amkai.aes.aescommon.utilities.PatientUtils;
import com.amkai.aes.generatedbeans.billingledger.Charge;
import com.amkai.aes.generatedbeans.cdmcasehistory.CdmCaseHistory;
import com.amkai.aes.generatedbeans.cdmcasehistory.ReadyToBillHistoryEntry;
import com.amkai.aes.generatedbeans.cdmcasehistory.SupplyUsageKind;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.phase.DepartmentLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.phase.ExtendedDepartmentLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.phase.PatDepartmentLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.procedure.ProcedureLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.procedure.ProcedureLogDiagnosisAssociation;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.resources.AdjustmentDescriptor;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.resources.ResourceLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.resources.SupplyLog;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.resources.TransactionDescriptor;
import com.amkai.aes.generatedbeans.cdmcasehistory.logdetails.resources.TrayLog;
import com.amkai.aes.generatedbeans.inventory.InventorySetupOptions;
import com.amkai.aes.generatedbeans.inventory.TransactionCalculationEffect;
import com.amkai.aes.generatedbeans.organization.Department;
import com.amkai.aes.generatedbeans.pmcase.PmCase;
import com.amkai.aes.generatedbeans.procedure.Procedure;
import com.amkai.aes.generatedbeans.registration.Patient;
import com.amkai.aes.generatedbeans.scheduling.AbstractAppointment;
import com.amkai.aes.server.billing.casecosting.CaseCostingProviderImpl;
import com.amkai.aes.server.billing.ledger.ServerLedgerEntryLoadManager;
import com.amkai.aes.server.casehistory.CaseHistoryUploader;
import com.amkai.aes.server.domaincommiter.casehistory.CaseHistoryLoader;
import com.amkai.aes.server.domaincommiter.casehistory.MainDomainCommitter;
import com.amkai.aes.server.manager.RegistrationManager;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.aes.webservice.binary.objecthandler.inventory.InventoryJobHandler;
import com.amkai.common.base.utils.AESDateFormatter;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;
import com.amkai.mbus.BusManager;
import com.amkai.mbus.spi.BusMessage;
import com.amkai.mbus.spi.Command;
import com.jgoodies.validation.ValidationResult;

public class CaseHistoryJobHandler implements JobObjectHandler<CaseHistoryJob, Serializable, CaseHistoryJobResult> {
	private static final Log log = LogFactory.getLog(CaseHistoryJobHandler.class);
	private static final String TITLE = "Case History Server";

	private static final String CASE_HISTORY_BY_PMCASE = "FROM CdmCaseHistory history WHERE history.pmCase.id = :pmCase";
	private static final String CASE_HISTORIES_BY_PATIENT = "FROM CdmCaseHistory history WHERE history.patient.id = :patient";

	@Override
	@SuppressWarnings("unchecked")
	public CaseHistoryJobResult process(final CaseHistoryJob job, final Serializable object) throws Exception {
		CaseHistoryJobResult result = new CaseHistoryJobResult();

		SessionManager sm = SiteManager.Start();
		sm.openSession();
		try {
			if (job.getKind() != null) {
				switch (job.getKind()) {
				case DEPLETE:
					if (job.getPassport() != null && job.getCaseHistories() != null) {

						List<AdjustmentDescriptor> adjustmentDescriptors = new ArrayList<AdjustmentDescriptor>();

						Set<CdmCaseHistory> histories = new HashSet<CdmCaseHistory>();
						Map<Long, CdmCaseHistory> hs = new HashMap<Long, CdmCaseHistory>();

						for (CdmCaseHistory row : job.getCaseHistories()) {
							CdmCaseHistory cdm = sm.get(CdmCaseHistory.class, row.getId());
							List<PatDepartmentLog> pdls = sm.execute("FROM PatDepartmentLog pd WHERE pd.cdmCaseHistory = :cdm ")
									.setEntity("cdm", cdm).list();
							cdm.getPatDepartmenLogs().addAll(pdls);
							adjustmentDescriptors.addAll(AdjustmentDescriptorHelper.getDepletableAdjustmentDescriptors(cdm));
							hs.put(cdm.getId(), cdm);
						}
						for (AdjustmentDescriptor ad : adjustmentDescriptors) {
							ad.getInventoryElement().getSupplyResourceItem().getBaseOrganization();
						}
						if (!adjustmentDescriptors.isEmpty()) {

							InventoryJobResult ijobResult = InventoryJobHandler.processAdjustments(sm, job.getModuleDepartment(),
									adjustmentDescriptors, null);

							// AES-9250
							if (ijobResult != null) {
								if (ijobResult.getException() == null
										|| (ijobResult.getException() != null && ijobResult.getException() instanceof AdjustmentManagerException)) {
									if (ijobResult.getAdjustmentDescriptors() != null && !ijobResult.getAdjustmentDescriptors().isEmpty()) {
										for (AdjustmentDescriptor adjustmentDescriptor : ijobResult.getAdjustmentDescriptors()) {
											if (adjustmentDescriptor.getSupplyLog() != null
													&& adjustmentDescriptor.getSupplyLog().getCdmCaseHistory() != null
													&& adjustmentDescriptor.getSupplyLog().getCdmCaseHistory().getId() != null) {
												histories.add(hs.get(adjustmentDescriptor.getSupplyLog().getCdmCaseHistory().getId()));
											}
										}
										result.setAdjustmentDescriptors(ijobResult.getAdjustmentDescriptors());
										if (adjustmentDescriptors.size() != ijobResult.getAdjustmentDescriptors().size()) {
											result.setRunErrorReport(true);
										}
										result.setException(ijobResult.getException());
										for (CdmCaseHistory history : histories) {
											sm.openTransaction();
											try {
												CdmCaseHistory casehistory = sm.get(CdmCaseHistory.class, history.getId());
												if (casehistory.getInitDepleteDate() == null) {
													casehistory.setInitDepleteBy(job.getPassport());
													casehistory.setInitDepleteDate(new Date());
												} else {
													casehistory.setLastDepleteBy(job.getPassport());
													casehistory.setLastDepleteDate(new Date());
												}
												sm.saveRAD(casehistory);
												sm.commitTransaction();
											} catch (Exception exception) {
												sm.rollbackTransaction();
												exception.printStackTrace();
												log.error("Group Deplete", exception);
												throw exception;
											}
										}
										Set<AppointmentStatusChangeResult> busMessageResult = new HashSet<AppointmentStatusChangeResult>();

										for (CdmCaseHistory history : histories) {
											CdmCaseHistory casehistory = sm.get(CdmCaseHistory.class, history.getId());
											if (casehistory != null && casehistory.getPmCase() != null
													&& casehistory.getPmCase().getAbstractAppointment() != null) {
												AbstractAppointment aa = sm.get(AbstractAppointment.class, casehistory.getPmCase()
														.getAbstractAppointment().getId());
												busMessageResult.add(new AppointmentStatusChangeResult(aa, null));
											}
										}

										if (busMessageResult != null && !busMessageResult.isEmpty()) {
											BusMessage message = new BusMessage(Command.SCHEDULE_DATA_CHANGED);
											message.addParameter("appointmentStatusChangeResults", busMessageResult);
											BusManager.getInstance().sendMessage(message);
										}

									}
								}
							}
						}
					}
					break;
				case DOWNLOAD:
					if (job.getPatientId() == null) {
						if (job.getPmCaseId() != null) {
							Query query = sm.execute(CASE_HISTORY_BY_PMCASE);
							query.setParameter("pmCase", job.getPmCaseId());

							CdmCaseHistory history = (CdmCaseHistory) query.uniqueResult();
							if (history != null) {
								CaseHistoryLoader caseHistoryLoader = new CaseHistoryLoader();
								history = caseHistoryLoader.load(sm, history);
								loadBillingStatus(sm,job,history);
								RegistrationManager rm = new RegistrationManager(sm);
								rm.loadBillingGroups(history.getPmCase(), null);
								result.setCaseHistory(history);

							}

							result.setCaseHistory(history);
						}
					} else {
						Query query = sm.execute(CASE_HISTORIES_BY_PATIENT);
						query.setParameter("patient", job.getPatientId());

						List<CdmCaseHistory> histories = new ArrayList<CdmCaseHistory>();
						for (CdmCaseHistory history : (List<CdmCaseHistory>) query.list()) {
							CaseHistoryLoader caseHistoryLoader = new CaseHistoryLoader();
							histories.add(caseHistoryLoader.load(sm, history));
						}

						result.setCaseHistories(histories);
					}
					break;
				case SEARCH:
					Query query = sm
							.execute("SELECT history.id FROM CdmCaseHistory AS history WHERE (history.pmCase.dateOfService BETWEEN :beginDate AND :endDate AND history.pmCase.organizationSpecificProperties.baseOrganization.id = :departmentId) AND EXISTS(FROM AdjustmentDescriptor AS adjustment WHERE adjustment.adjustment IS NULL AND adjustment.kindWrapper.id NOT IN(:kind) AND adjustment.supplyLog.cdmCaseHistory = history)");
					query.setParameter("beginDate", job.getSearchParam().getBeginDate());
					query.setParameter("endDate", job.getSearchParam().getEndDate());
					query.setLong("departmentId", job.getModuleDepartment().getId());
					query.setParameterList("kind", Arrays.asList(SupplyUsageKind.CDM_USAGE_COUNT.ordinal(), SupplyUsageKind.PHYSICAL_ADD.ordinal(),
							SupplyUsageKind.PHYSICAL_SUB.ordinal()));

					List<CdmCaseHistory> histories = new ArrayList<CdmCaseHistory>();
					for (Long historyId : (List<Long>) query.list()) {
						// CaseHistoryLoader caseHistoryLoader = new
						// CaseHistoryLoader();

						CdmCaseHistory history = sm.get(CdmCaseHistory.class, historyId);
						// KKORNEL: AES-17699
						history.getPatient().getPerson().getDisplayName();

						history.getPmCase();

						history.getPmCase().getDateOfService();
						Collection<AdjustmentDescriptor> adjustmentDescriptors = getAdjustmentDescriptors(sm, history);
						boolean hasError = false;
						if (AdjustmentDescriptorHelper.validateTransactionDescriptors(adjustmentDescriptors).hasErrors()) {
							hasError = true;
						} else if (validateAdjustmentDescriptors(sm, job.getModuleDepartment(), adjustmentDescriptors, false).hasErrors()) {
							hasError = true;
						}
						history.putTranzientProperty("ERROR", new Boolean(hasError));
						histories.add(history);
						sm.clear();

					}

					result.setCaseHistories(histories);
					break;
				case UPLOAD:
					sm.openTransaction();
					try {
						CaseHistoryUploader caseHistorySaver = new CaseHistoryUploader();
						caseHistorySaver.upload(sm, job.getCaseHistory());
						sm.commitTransaction();
					} catch (Exception exception) {
						sm.rollbackTransaction();
						log.error("Case history load", exception);
						throw exception;
					}
					break;
				case COMMIT_DOMAIN:
					try {
						MainDomainCommitter mdc = new MainDomainCommitter();
						mdc.setNeedReleaseLock(job.isNeedReleaseLock());						
						result.setDomain(mdc.commitDomain(job.getDomain(), (Department) object, null));
						
					} catch (Exception ex) {
						result.setException(ex);
						log.error(TITLE, ex);
					}
					break;
				case DOWNLOAD_PROCEDURE:
					Procedure procedure = sm.get(Procedure.class, job.getProcedureID());
					ProcedureUtils.loadProcedure(procedure);
					result.setProcedure(procedure);
					break;
				case GET_PROCEDURE_LOGS:
					ExtendedDepartmentLog edl = sm.get(ExtendedDepartmentLog.class, job.getExtendedDepartmentLog().getId());
					for (ProcedureLog procedureLog : edl.getProcedureLogs()) {
						for (Charge charge : ProcedureLogUtils.getCharges(procedureLog)) {
							ServerLedgerEntryLoadManager.loadEntry(charge, sm, false, edl.getCdmCaseHistory().getPmCase().getDepartment());
						}

						AbstractAppointment aa = procedureLog.getExtendedDepartmentLog().getAbstractAppointment();
						AbstractAppointmentUtils.loadAbstractAppointment(aa);

						SourceOfRevenueUtils.loadSourceOfRevenue(procedureLog.getSourceOfRevenue());
						FacilityManager.loadFacility(procedureLog.getFacility());
						ProcedureUtils.loadProcedure(procedureLog.getProcedure());
						ProcedureUtils.loadProcedure(procedureLog.getAsaAnesProcedure());
						ProcedureUtils.loadProcedure(procedureLog.getCmsAnesProcedure());

						for (ProcedureLogDiagnosisAssociation assoc : procedureLog.getProcedureLogDiagnosisAssociations()) {
							assoc.getProcedureLog().getId();
							ItemManager.loadItem(assoc.getDiagnosis());
							// KKORNEL: AES-13961
							ItemManager.loadItem(assoc.getPresentOnAdmission());
						}
						ProcedureLogUtils.loadProcedureLog(procedureLog);
					}
					result.setProcedureLogs(edl.getProcedureLogs());
					break;
				case GET_ALL_RESOURCE_LOGS:
					CdmCaseHistory cch = sm.get(CdmCaseHistory.class, job.getCaseHistory().getId());
					for (ResourceLog rl : cch.getResourceLogs()) {
						if (rl instanceof SupplyLog)
							SupplyLogUtils.loadSupplyLog((SupplyLog) rl);
						else if (rl instanceof TrayLog)
							loadTrayLog((TrayLog) rl);
						else
							ResourceLogUtils.loadResourceLog(rl);
					}
					result.setResourceLogs(cch.getResourceLogs());
					break;
				case GET_RESOURCE_LOGS_BY_DEPARTMENT_LOG:
					DepartmentLog dl = sm.get(DepartmentLog.class, job.getDepartmentLog().getId());
					for (ResourceLog rl : dl.getResourceLogs()) {
						if (rl instanceof SupplyLog)
							SupplyLogUtils.loadSupplyLog((SupplyLog) rl);
						else if (rl instanceof TrayLog)
							loadTrayLog((TrayLog) rl);
						else
							ResourceLogUtils.loadResourceLog(rl);
					}
					result.setResourceLogs(dl.getResourceLogs());
					break;
				case GET_PMCASES_BY_PATIENT:
					Patient patient = sm.get(Patient.class, job.getPatientId());
					// KKORNEL: AES-13104
					List<PmCase> pmCases = PatientUtils.getSortedPmCasesByDOS(patient, job.getModuleDepartment());
					// List<PmCase> pmCases = new ArrayList<PmCase>();
					// pmCases.addAll(PatientUtils.getPmCases(patient,
					// job.getModuleDepartment()));
					// Collections.sort(pmCases, new Comparator<PmCase>() {
					// @Override
					// public int compare(PmCase first, PmCase second) {
					// int result =
					// first.getDateOfService().compareTo(second.getDateOfService());
					// if (result == 0) {
					// result =
					// first.getName().compareToIgnoreCase(second.getName());
					// }
					// return result;
					// }
					// });
					RegistrationManager rm = new RegistrationManager(sm);
					for (PmCase pmCase : pmCases) {
						rm.loadCase(pmCase, PatientUtils.getOrganizationSpecificProperties(patient, job.getModuleDepartment()));
						PatientUtils.getOrganizationSpecificProperties(pmCase.getPatient()); 
						PatientUtils.getReferringDoctor(pmCase.getPatient(), job.getModuleDepartment());
						if (pmCase.getAbstractAppointment() != null) {
							AbstractAppointmentUtils.loadAbstractAppointment(pmCase.getAbstractAppointment());
						}
					}
					result.setPmCases(pmCases);
					break;
				// #14695
				case STORE_READYTOBILL_STATUS_HISTORY: {
					ReadyToBillStatusHistoryDataManager.storeReadyToBillStatusEntry(sm, job.getReadyToBillHistoryEntry());
					break;
				}
					// #14695
				case GET_READYTOBILL_STATUS_HISTORY: {
					List<ReadyToBillHistoryEntry> readyToBillHistoryEntries = ReadyToBillStatusHistoryDataManager.getReadyToBillStatusEntry(sm, job
							.getCaseHistory());
					result.setReadyToBillHistoryEntries(readyToBillHistoryEntries);
					break;
				}
				}
			}
		} finally {
			sm.closeSession();
		}
		return result;
	}

	private void loadTrayLog(TrayLog trayLog) {
		if (trayLog != null) {
			if (trayLog.getTray() != null) {
				if (trayLog.getTray().getType() != null)
					trayLog.getTray().getType().name();
			}
			for (SupplyLog supplyLog : trayLog.getOnTraySupplyLogs()) {
				SupplyLogUtils.loadSupplyLog(supplyLog);
			}
		}
	}

	public static ValidationResult validateAdjustmentDescriptors(SessionManager sm, Department department,
			Collection<AdjustmentDescriptor> adjustmentDescriptors, boolean additionText) {
		ValidationResult result = new ValidationResult();
		String query = "select iso from InventorySetupOptions iso where iso.baseOrganization.id = :depId";
		InventorySetupOptions options = (InventorySetupOptions) sm.execute(query).setLong("depId", department.getId()).uniqueResult();

		for (AdjustmentDescriptor adjustmentDescriptor : adjustmentDescriptors) {

			String text = "";
			if (additionText) {
				text = " (Supply/Resource Screen)";
				if (adjustmentDescriptor.getSupplyLog() != null && adjustmentDescriptor.getSupplyLog().getTrayLog() != null) {
					text = " (Trays Screen)";
				}
			}

			String id = (adjustmentDescriptor.getInventoryElement() != null && adjustmentDescriptor.getInventoryElement().getId() != null ? adjustmentDescriptor
					.getInventoryElement().getId().toString()
					: "");

			for (TransactionDescriptor transactionDescriptor : adjustmentDescriptor.getTransactionDescriptors()) {
				if (adjustmentDescriptor.getAdjustment() == null && adjustmentDescriptor.getTransactionCode() != null
						&& adjustmentDescriptor.getTransactionCode().getCalculationEffect() != TransactionCalculationEffect.ADD) {

					// Integer quantity = (Integer)
					// transactionDescriptor.getSupplyResourceLocation().getTranzientProperty(InventoryJobResult.SUPPLY_LOCATION_QOvH
					// + id);
					Integer quantity = (Integer) sm.execute(
							"SELECT il.quantityOnHand FROM ItemLocation il WHERE il.inventoryElement = :ie AND il.location = :loc ").setEntity("ie",
							adjustmentDescriptor.getInventoryElement()).setEntity("loc", transactionDescriptor.getSupplyResourceLocation())
							.uniqueResult();

					if (quantity != null && quantity < transactionDescriptor.getQuantity() && !options.isAllowNegative()) {
						result.addError("Not enough supply/resource in this location" + text);
					}

					if (transactionDescriptor.getSerialNumber() != null) {
						Object valid = transactionDescriptor.getSerialNumber().getTranzientProperty(InventoryJobResult.SERIAL_NUMBER);
						if (valid != null && valid instanceof Boolean && !((Boolean) valid)) {
							result.addError("Serial number not exists on location" + text);
						}

					}

					if (transactionDescriptor.getExpirationDate() != null && transactionDescriptor.getSupplyResourceLocation() != null) {
						Long q = (Long) transactionDescriptor.getSupplyResourceLocation()
								.getTranzientProperty(
										InventoryJobResult.EXPIRATION + id
												+ AESDateFormatter.sqlDateFormat.format(transactionDescriptor.getExpirationDate()));
						if (q != null && q < transactionDescriptor.getQuantity() && !options.isAllowNegative()) {
							result.addError("Expired item not enough on location" + text);
						}
					}
				}
			}
		}
		return result;
	}

	
	public static Collection<AdjustmentDescriptor> getAdjustmentDescriptors(SessionManager sm, CdmCaseHistory caseHistory) {
		Collection<AdjustmentDescriptor> result = new HashSet<AdjustmentDescriptor>();
		List<DepartmentLog> dls = new ArrayList<DepartmentLog>();

		if (caseHistory != null) {
			dls.addAll(caseHistory.getPatDepartmenLogs());
			dls.add(caseHistory.getPreOpLog());
			dls.add(caseHistory.getAnesthesiaDepartmentLog());
			dls.add(caseHistory.getOperativeDepartmentLog());
			dls.add(caseHistory.getPacuLog());
			dls.add(caseHistory.getStepDownLog());
			dls.add(caseHistory.getRecoveryDepartmentLog());
			result = getAdjustmentDescriptors(sm, dls);
		}

		return result;
	}

	public static Collection<AdjustmentDescriptor> getAdjustmentDescriptors(SessionManager sm, List<DepartmentLog> dls) {
		Collection<AdjustmentDescriptor> result = new HashSet<AdjustmentDescriptor>();

		if (dls != null && dls.size() > 0) {
			result.addAll(sm.execute("FROM AdjustmentDescriptor ad WHERE ad.supplyLog.departmentLog in (:dls) ").setParameterList("dls", dls).list());
		}

		return result;
	}
	
	protected void loadBillingStatus(SessionManager sm, CaseHistoryJob job, CdmCaseHistory cdm ) throws Exception{
		CaseCostingProviderImpl ccp = new CaseCostingProviderImpl(sm, job.getModuleDepartment());
		// TODO: AES-17784
		List<CdmLogBillingStatus> cdmlogs = ccp.getCdmLogBillingStatusRows(cdm, true, true, null);
		for (CdmLogBillingStatus cdmLogBillingStatus : cdmlogs) {
			switch (cdmLogBillingStatus.getKind()) {
			case DEPARTMENT:
				DepartmentLog dl = sm.get(DepartmentLog.class, cdmLogBillingStatus.getLogId());
				dl.putTranzientProperty("BILLING_STATUS", cdmLogBillingStatus);
				
				break;
			case PROCEDURE:
				ProcedureLog pl = sm.get(ProcedureLog.class, cdmLogBillingStatus.getLogId());
				pl.putTranzientProperty("BILLING_STATUS", cdmLogBillingStatus);
				break;
			case SUPPLY:
				SupplyLog sl = sm.get(SupplyLog.class, cdmLogBillingStatus.getLogId());
				sl.putTranzientProperty("BILLING_STATUS", cdmLogBillingStatus);
				break;

			default:
				break;
			}
		}

	}

}
